<?php

namespace qore_base\util\collections;
use \qore\error\Exception;
use \Qore;

/**
 * @author Samuel Nogueira <samuel.nogueira.dev@gmail.com>
 */
class Map extends Collection implements \ArrayAccess {
	/**
	 * @var array internal data storage
	 */
	private $_data = array();
	
	/**
	 * Returns an iterator for traversing the items in tthe map
	 * @return qore\util\collections\MapIterator
	 */
	public function getIterator() {
		return new MapIterator($this->_data);
	}

	/**
	 * Returns the number of items in the map.
	 * @return integer the number of items in the map
	 */
	public function getCount() {
		return count($this->_data);
	}

	/**
	 * @return array the key list
	 */
	public function getKeys() {
		return array_keys($this->_data);
	}
	
	/**
	 * Adds an item into the map.
	 * Note, if the specified key already exists, the old value will be overwritten.
	 * @param mixed $key key
	 * @param mixed $value value
	 */
	public function add($key, $value) {
		$this->_data[$key] = $value;
	}

	/**
	 * Removes an item from the map by its key.
	 * @param mixed $key the key of the item to be removed
	 * @return mixed the removed value, null if no such key exists.
	 */
	public function remove($key) {
		$value = $this->offsetExists($key) ? $this->offsetGet($key) : null;
		unset($this->_data[$key]);
		return $value;
	}

	/**
	 * Removes all items in the map.
	 */
	public function clear() {
		$this->_data = array();
	}

	/**
	 * @param mixed $value the value
	 * @return boolean whether the map contains an item with such value
	 */
	public function contains($value) {
		return in_array($value, array_values($this->_data));
	}

	/**
	 * @param string $key The key
	 * @return boolean Whether the map contains the given key
	 */
	public function containsKey($key) {
		return isset($this->_data[$key]) || array_key_exists($key, $this->_data);
	}

	/**
	 * @return array The map as an array
	 */
	public function toArray() {
		return $this->_data;
	}

	/**
	 * Copies iterable data into the map.
	 * @param mixed $data the data to be copied from, must be an array or object implementing Traversable
	 * @throws Exception If data is neither an array nor an iterator.
	 */
	public function addAll($data) {
		if (!$data) return;
		if (!is_array($data) && !($data instanceof Traversable)) {
			throw new Exception(Qore::t('qore', 'Map data must be an array or an object implementing Traversable.'));
		}

		foreach ($data as $key => $value) {
			$this->add($key, $value);
		}
	}

	/**
	 * Returns whether there is an element at the specified key.
	 * This method is required by the interface ArrayAccess.
	 * @param string $key.
	 * @return boolean
	 */
	public function offsetExists($key) {
		// we do both verifications for performance considerations
		return isset($this->_data[$key]) || array_key_exists($key, $this->_data);
	}

	/**
	 * Returns an element.
	 * This method is required by the interface ArrayAccess.
	 * @param string $key The key to retrieve the element.
	 * @return mixed the element at the given key, null if no element is found
	 */
	public function offsetGet($key) {
		return isset($this->_data[$key]) ? $this->_data[$key] : null;
	}

	/**
	 * Sets an element.
	 * This method is required by the interface ArrayAccess.
	 * @param string $key The key to set the element.
	 * @param mixed $item the element value
	 */
	public function offsetSet($key, $item) {
		$this->add($key, $item);
	}

	/**
	 * Unsets an element.
	 * This method is required by the interface ArrayAccess.
	 * @param string $key The key to unset the element.
	 */
	public function offsetUnset($key) {
		$this->remove($key);
	}
}
